const { User } = require('../models')
const bcrypt = require('bcryptjs')
const { Existing, AuthFailed, NotFound } = require('../core/http-exception')
const { redis, generateToken, isImgExists, isModelOne, createID } = require('../common')
const { authCode, server } = require('../config')
const { USER, ADMIN, SP_ADMIN, getLevel, getScope } = require('../middleware/auth')
const { Op } = require('sequelize')
const xss = require('xss')

class UserController{
  static async findUser(data, flag, exclude = []){
    let t = flag ? {email: data} : data
    const user = await User.findOne({
      where: t,
      attributes: {
        exclude
      }
    })

    if (!user) {
      throw new NotFound('用户不存在')
    }

    return user;
  }

  // 创建用户
  static async create(data){
    const hasUser = await User.findOne({
      where: {
        email: data.get('email')
      }
    })
    if(hasUser){
      throw new Existing('用户已存在');
    }

    const isDev = server.environment === 'dev';
    if(!isDev){
      const key = data.get('email') + '-' + authCode.register_user
      const code = await redis.get('authCode', key)

      if (!code || code !== data.get('authCode')) {
        throw new AuthFailed('无效的验证码')
      }
      await redis.del('authCode', key)
    }

    let users = await User.count();

    const scope = users <= 0 ? SP_ADMIN : USER;

    const user = await User.create({
      username: data.get('username'),
      email: data.get('email'),
      password: data.get('password'),
      uid: createID(data.get('email')),
      scope
    });

    return {
      username: user.username,
      email: user.email
    }
  }

  // 登录
  static async login(data){
    const user = await User.findOne({
      where: {
        email: data.get('email')
      }
    })

    if(!user){
      throw new AuthFailed('账号不存在或密码不正确');
    }

    const correct = bcrypt.compareSync(data.get('password'), user.password);
    if(!correct){
      throw new AuthFailed('账号不存在或密码不正确')
    }

    const token = generateToken(user.id, user.uid, user.scope);
    await user.update({
      token
    })

    return {
      id: user.uid,
      username: user.username,
      portrait: user.portrait,
      scope: getLevel(user.scope),
      token,
    };
  }

  // 获取用户数据
  static async getInfo(data, auth){
    let id = data && data.get('uid') ? {uid: data.get('uid')} : auth ? {id: auth.id} : null
    const key = `user_info_id_${id}`
    const cache = await redis.get('content', key);
    if(cache){
      const result = JSON.parse(cache);
      result.cache = true;
      return result;
    }


    const user = await this.findUser(id, false, ['deleted_at', 'updated_at', 'password', 'token', 'email', 'id']);
    user.scope = getLevel(user.scope);
    return user;
  }

  static async update(data, ctx){
    if(data.get('uid') != ctx.auth.uid && ctx.auth.scope < ADMIN) throw new AuthFailed('你没有权限修改')
    const user = await this.findUser({ uid: data.get('uid') })

    if(data.get('cover')){
      await isImgExists(data.get('cover'));
    }

    if(data.get('portrait')){
      await isImgExists(data.get('portrait'));
    }

    await user.update({
      information: data.get('information', user.information),
      web: data.get('web', user.web),
      github: data.get('github', user.github),
      company: data.get('company', user.company),
      cover: data.get('cover', user.cover),
      portrait: data.get('portrait', user.portrait)
    })
  }

  // 修改权限
  static async setScope(data, auth){
    if(data.get('uid') == auth.uid) {
      throw new AuthFailed('不能设置自己的权限');
    }
    const user = await this.findUser({uid: data.get('uid')})

    // if(parseInt(data.get('scope')) >= SP_ADMIN){
    //   throw new AuthFailed('超级管理员是唯一的');
    // }
    const scope = getScope(data.get('scope'))
    if(!scope){
      throw new AuthFailed('错误的权限参数')
    }

    await user.update({
      scope: scope
    })
  }

  // 修改密码
  static async updatePassword(data, auth){
    let user;
    if(auth){
      user = await this.findUser({id: auth.id})
    } else {
      user = await this.findUser(data.get('email'), true)
    }

    const key = user.email + '-' + authCode.update_password
    const code = await redis.get('authCode', key)

    if (!code || code !== data.get('authCode')) {
      throw new AuthFailed('无效的验证码')
    }

    const correct = bcrypt.compareSync(data.get('password'), user.password)
    if(correct){
      throw new AuthFailed('新密码不能和旧密码一致')
    }

    await redis.del('authCode', key)

    const token = auth ? generateToken(user.id, user.uid, user.scope) : null;

    await user.update({
      password: data.get('password'),
      token
    })

    return token;
  }

  // 列表
  static async list(data, auth){
    const { page = 1, size = 10, order = 'DESC', keyword, id } = data.get();

    if(auth.scope < ADMIN && keyword == ''){
      throw new AuthFailed('只能获取相关用户');
    }
    const key = `user_list_page_${page}_size_${size}_keyword_${keyword}_id_${id}_order_${order}`
    const cache = await redis.get('content', key)
    if (cache) {
      let res = JSON.parse(cache)
      res.cache = true
      return res
    }
    let filter = {
    }

    if (id) {
      filter.uid = id;
    }

    if (keyword) {
      filter.username = {
        [Op.like]: `%${xss(keyword)}%`
      }
    }

    const users = await User.findAndCountAll({
      limit: parseInt(size),
      offset: (page - 1) * parseInt(size),
      where: filter,
      order: [['created_at', order]],
      attributes: {
        exclude: ['deleted_at', 'password', 'token']
      }
    })

    const result = {
      list: users.rows.map(item => {
        item.scope = getLevel(item.scope)
        return item;
      }),
      meta: {
        total: users.count,
        page_size: size,
        current_page: parseInt(page),
        total_pages: Math.ceil(users.count / size)
      }
    }

    await redis.set('content', key, JSON.stringify(result), 60)

    return result
  }

  static async logout(auth){
    const user = await isModelOne('user', auth.id);

    await user.update({
      token: null
    })
  }

  static async test(data, auth){
    if(!data.get('overtime')) return;
    const token = generateToken(auth.id, auth.uid, auth.scope)
    return token;
  }
}

module.exports = UserController